//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.WebClient.Common
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using Common;
    using SR = Microsoft.Research.eResearch;
    using Microsoft.Research.ScientificWorkflow.TridentModelSL;
    using System.Globalization;
    using Microsoft.Research.ScientificWorkflow.WorkflowApplicationCommonSL;

    public static class RegistryService
    {
        #region Public methods

        /// <summary>
        /// Gets the user.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <param name="conn">The conn.</param>
        /// <returns></returns>
        public static SR.User GetUserByName(string userName, SR.Connection conn)
        {
            return SR.User.Load(userName, conn);
        }

        /// <summary>
        /// Gets the user role.
        /// </summary>
        /// <param name="conn">The conn.</param>
        /// <param name="user">The user.</param>
        /// <returns></returns>
        public static UserRole GetUserRole(SR.Connection conn, SR.User user)
        {
            try
            {
                SR.SystemPolicy pol = SR.SystemPolicy.Get(conn);

                SR.SystemRole userAdminRole = user.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SR.SystemBuiltinRoleType.Administrators));

                if (userAdminRole != null)
                {
                    return UserRole.Admin;
                }

                SR.SystemRole serviceRole = user.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SR.SystemBuiltinRoleType.Services));
                if (serviceRole != null)
                {
                    // Service will be treated as Admin
                    return UserRole.Admin;
                }
                SR.SystemRole userRole = user.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SR.SystemBuiltinRoleType.Users));
                if (userRole != null)
                {
                    return UserRole.NormalUser;
                }

                SR.SystemRole guestRole = user.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SR.SystemBuiltinRoleType.Guests));
                if (guestRole != null)
                {
                    return UserRole.Guest;
                }

                return UserRole.InValid;

            }
            catch (SR.RecordNotFoundException)
            {
                return UserRole.InValid;
            }
        }

        /// <summary>
        /// Gets the all workflow categories.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <param name="conn">The conn to use to the server </param>
        /// <returns></returns>
        public static Collection<SR.Namespace> GetWorkflowCategories(SR.Connection conn)
        {
            Collection<SR.Namespace> categories = new Collection<SR.Namespace>();
            SR.User systemUser = SR.User.Load(Constants.SYSTEM_USER_NAME, conn);
            systemUser.WorkflowRoot.Refresh();
            categories.Add(systemUser.WorkflowRoot);
            return categories;
        }

        /// <summary>
        /// Gets all users my workflows.
        /// </summary>
        /// <param name="categories">The categories.</param>
        public static Collection<SR.Namespace> GetAllUsersMyWorkflows(SR.Connection registryConnection, SR.User currentUser)
        {
            Collection<SR.Namespace> allUserCategories = new Collection<SR.Namespace>();
            List<SR.User> allUsers = SR.User.LoadAll(registryConnection);
            foreach (SR.User user in allUsers)
            {
                if ((user as SR.IObject).ID != (currentUser as SR.IObject).ID)
                {
                    Collection<SR.Namespace> currentUserCat = GetMyWorkflowsForUser(user);
                    foreach (SR.Namespace ns in currentUserCat)
                    {
                        allUserCategories.Add(ns);
                    }
                }
            }
            return allUserCategories;
        }

        /// <summary>
        /// Gets the users my workflows.
        /// </summary>
        /// <param name="categories">The categories.</param>
        public static Collection<SR.Namespace> GetMyWorkflowsForUser(SR.User user)
        {
            Collection<SR.Namespace> categories = new Collection<SR.Namespace>();
            try
            {

                if (user != null)
                {
                    SR.Namespace userRoot = user.WorkflowRoot;
                    if (userRoot != null)
                    {
                        userRoot.Refresh();
                        SR.Namespace currentUserRoot = userRoot.Children.Where(
                            child => child.User != null).FirstOrDefault(
                            child => child.User.Equals(user));

                        if (currentUserRoot != null)
                        {
                            categories.Add(currentUserRoot);
                        }
                    }
                }

            }
            catch { }
            return categories;
        }

        /// <summary>
        /// Gets all shared workflows.
        /// </summary>
        /// <returns></returns>
        public static List<SR.Activity> GetAllSharedWorkflowsForCurrentUser(SR.Connection registryConnection, SR.User user)
        {
            List<SR.Activity> allPermittedActivities = new List<SR.Activity>();
            try
            {
                Collection<SR.User> restrictedUsers = new Collection<SR.User>();
                SR.User systemUser = SR.User.Load(Constants.SYSTEM_USER_NAME, registryConnection);
                restrictedUsers.Add(user);
                restrictedUsers.Add(systemUser);
                Collection<SR.Activity> workflows = SR.Activity.CommonSearches.GetWorkflowsNotOwnedBy(registryConnection, restrictedUsers);
                allPermittedActivities = workflows.Where(workflow => CheckActivityValidityForNormalUser(workflow)).ToList();
            }
            catch (SR.RecordNotFoundException) { }

            return allPermittedActivities;
        }

        /// <summary>
        /// Checks the activity validity.
        /// </summary>
        /// <param name="activity">The activity.</param>
        /// <returns></returns>
        private static bool CheckActivityValidityForNormalUser(SR.Activity activity)
        {
            bool validActivity = false;
            if (TridentApplication.Instance.User != null
                && (activity.Security.Rights.AuthorWorkflow.Check(TridentApplication.Instance.User)
                    || activity.Security.Rights.RunWorkflow.Check(TridentApplication.Instance.User)))
            {
                validActivity = true;
            }

            return validActivity;
        }

        /// <summary>
        /// Gets the workflow by id.
        /// </summary>
        /// <param name="workflowId">The workflow id.</param>
        /// <param name="conn">The conn.</param>
        /// <returns></returns>
        public static SR.Activity GetWorkflowById(Guid workflowId, SR.Connection conn)
        {
            SR.Activity workflow = null;
            try
            {
                workflow = SR.Activity.Load(workflowId, conn);

                if (workflow != null && workflow.IsDeleted)
                {
                    workflow = null;
                }
            }
            catch (System.Security.SecurityException ex)
            {
                TridentErrorHandler.ThrowTridentCustomException(WorkflowApplicationResources.WorkflowPermissionDenied, ex);
            }
            catch (SR.BackendStorageException ex)
            {
                TridentErrorHandler.ThrowTridentCustomException(WorkflowApplicationResources.RegistryConnectionLost, ex);
            }
            catch (Exception ex)
            {
                TridentErrorHandler.ThrowTridentCustomException(WorkflowApplicationResources.WorkflowRetrievalError, ex);
            }

            return workflow;
        }

        /// <summary>
        /// Gets all the available nodes to execute the job on
        /// </summary>
        /// <param name="isInteractive">if set to <c>true</c> [is interactive].</param>
        /// <param name="conn">The conn.</param>
        /// <returns></returns>
        public static List<SR.Machine> GetAvailableNodes(bool isInteractive, SR.Connection conn)
        {
            List<SR.Machine> nodes = new List<SR.Machine>();
            try
            {
                nodes = SR.Machine.LoadAll(conn);
                IEnumerable<SR.Machine> availableNodes = nodes.Where(mac => (mac.Availability == SR.ExecutorAvailabiltiy.Online) && (mac.IsDeleted == false) && (mac.IsInteractiveExecution == isInteractive));
                nodes = availableNodes.ToList();
            }
            catch (Exception ex)
            {
                TridentErrorHandler.ThrowTridentCustomException(WorkflowApplicationResources.NodeRetrievalError, ex);
            }
            return nodes;
        }


        /// <summary>
        /// Create a job in the registry. The executer picks this job and runs the workflow.
        /// </summary>
        /// <param name="jobDetails">The job details instance. Contains the details for the creation of the job.</param>
        /// <param name="registryConnection">The registry connection.</param>
        /// <returns>The new job created.</returns>
        public static SR.Job ExecuteWFOnMachine(JobDescriptionModel jobDetails, SR.User scheduledUser, SR.Connection registryConnection)
        {
            if (jobDetails == null)
            {
                throw new ArgumentNullException("jobDetails");
            }
            if (registryConnection == null)
            {
                throw new ArgumentNullException("registryConnection");
            }

            SR.Job newJob = null;

            try
            {
                SR.Machine machineToRun = SR.Machine.Load(jobDetails.NodeToExecuteOn.NodeId, registryConnection);

                if (machineToRun.Availability == SR.ExecutorAvailabiltiy.Online)
                {
                    SR.Activity workflowToExecute = SR.Activity.Load(jobDetails.workflowId, registryConnection);

                    if (!workflowToExecute.Security.Rights.RunWorkflow.Check(scheduledUser))
                    {
                        TridentErrorHandler.ThrowTridentCustomException(WorkflowApplicationResources.WorkflowPermissionDenied);
                    }

                    SR.ActivityInstance actInstance = SR.ActivityInstance.Create(workflowToExecute, registryConnection);
                    actInstance.Save();

                    // Create a new Job instance for running the worfklow.
                    string localJobName =
                        !string.IsNullOrEmpty(jobDetails.JobName) ? jobDetails.JobName : string.Format(CultureInfo.CurrentCulture, "{0} - {1}", workflowToExecute.Name, DateTime.Now);

                    newJob = SR.Job.Create(localJobName, false, registryConnection);

                    // If user is null then get the current user.
                    if (null == jobDetails.ScheduledBy || jobDetails.ScheduledBy.UserId.Equals(Guid.Empty))
                    {
                        newJob.ScheduledBy = scheduledUser;
                    }


                    newJob.Notes = jobDetails.JobNotes;
                    newJob.Save();

                    RegistryService.CreateJobRequiredParameters(actInstance, jobDetails.RequiredParameters, registryConnection);

                    newJob.Machines.Add(machineToRun);
                    newJob.ActivityInstances.Add(actInstance);

                    // Set this at the end. After all values are assigned.
                    newJob.Status = SR.JobStatus.Waiting;
                    newJob.Save();
                    newJob.Refresh();
                }
                else
                {
                    TridentErrorHandler.ThrowTridentCustomException(WorkflowApplicationResources.InvalidNodeSelected);
                }
            }
            catch (TridentCustomException)
            {
                throw;
            }
            catch (System.Security.SecurityException ex)
            {
                TridentErrorHandler.ThrowTridentCustomException(WorkflowApplicationResources.WorkflowPermissionDenied, ex);
            }
            catch (SR.BackendStorageException ex)
            {
                TridentErrorHandler.ThrowTridentCustomException(WorkflowApplicationResources.RegistryConnectionLost, ex);
            }
            catch (Exception ex)
            {
                TridentErrorHandler.ThrowTridentCustomException(WorkflowApplicationResources.JobCreationError, ex);
            }

            return newJob;
        }


        /// <summary>
        /// Terminates the job.
        /// </summary>
        /// <param name="jobId">The job id.</param>
        /// <param name="registryConnection">The registry connection.</param>
        public static void TerminateJob(Guid jobId, SR.Connection registryConnection)
        {
            try
            {
                SR.Job jobToTerminate = SR.Job.Load(jobId, registryConnection);
                if (jobToTerminate != null)
                {
                    if (jobToTerminate.Status == SR.JobStatus.Aborted || jobToTerminate.Status == SR.JobStatus.Completed)
                    {
                        TridentErrorHandler.ThrowTridentCustomException(WorkflowApplicationResources.WorkflowExecutionCompleted);
                    }
                    if (jobToTerminate.Status == SR.JobStatus.Waiting)
                    {
                        jobToTerminate.Status = SR.JobStatus.Aborted;
                    }
                    else
                    {
                        jobToTerminate.Status = SR.JobStatus.StopPending;
                    }
                    jobToTerminate.ErrorMessage = string.Format("{0} [{1} ({2})].",
                            WorkflowApplicationResources.TerminatedByUser,
                            TridentApplication.Instance.User.Name,
                            TridentApplication.Instance.UserRole.ToString());
                    jobToTerminate.Save();
                }
            }
            catch (TridentCustomException)
            {
                throw;
            }
            catch (Exception ex)
            {
                TridentErrorHandler.ThrowTridentCustomException(WorkflowApplicationResources.WorkflowTerminateError, ex);
            }
        }


        /// <summary>
        /// Gets the workflowoutputs.
        /// </summary>
        /// <param name="jobId">The job id.</param>
        /// <param name="registryConnection">The registry connection.</param>
        /// <param name="getOutputs">if set to <c>true</c> [get outputs].</param>
        /// <returns></returns>
        public static Collection<SR.DataProduct> GetWorkflowoutputs(Guid jobId, SR.Connection registryConnection, bool getOutputs)
        {
            Collection<SR.DataProduct> dataproducts = new Collection<Microsoft.Research.eResearch.DataProduct>();

            try
            {
                SR.Job job = SR.Job.Load(jobId, registryConnection);
                job.ActivityInstances.GetEnumerator();
                foreach (SR.DataProduct dataproduct in job.ActivityInstances[0].Outputs)
                {
                    if (getOutputs)
                    {
                        if (dataproduct.IsFinal)
                        {
                            dataproducts.Add(dataproduct);
                        }
                    }
                    else
                    {
                        if (!dataproduct.IsFinal)
                        {
                            dataproducts.Add(dataproduct);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.ThrowTridentCustomException(WorkflowApplicationResources.WorkflowOutputsError, ex);
            }
            return dataproducts;
        }
        #endregion

        #region Private methods
        /// <summary>
        /// Create the required job param entries for the workflow in the required params table.
        /// </summary>
        /// <param name="job">The job instance.</param>
        /// <param name="workflowParamInfo">The workflow info.</param>
        private static void CreateJobRequiredParameters(SR.ActivityInstance actInstance, List<ParameterDescriptionModel> workflowParamInfo, SR.Connection registryConnection)
        {
            // Load all assigned parameters.
            List<SR.ParameterAssignment> activityParameterAssignments = new List<SR.ParameterAssignment>();

            try
            {
                RegistryService.GetParameterAssignments(actInstance.Activity.BaseSequence, activityParameterAssignments);

                foreach (ParameterDescriptionModel workflowRequiredParameter in workflowParamInfo)
                {
                    SR.ParameterAssignment reqAssignment = null;

                    // Get the activity unique name.
                    foreach (SR.ParameterAssignment parameter in activityParameterAssignments)
                    {
                        Console.WriteLine(parameter.ActivityParameter.ParameterClass);
                        if (parameter.Instance.Name.Equals(workflowRequiredParameter.ActivityUniqueId) &&
                            parameter.ActivityParameter.Name.Equals(workflowRequiredParameter.PropertyName))
                        {
                            reqAssignment = parameter;
                            break;
                        }
                    }

                    if (reqAssignment != null)
                    {
                        SR.ActivityInstanceParameter reqParam = SR.ActivityInstanceParameter.Create(
                            workflowRequiredParameter.Value.ToString(),
                            actInstance,
                            reqAssignment,
                            registryConnection);

                        reqParam.Save();
                    }
                }
            }
            catch (Exception)
            {
            }
        }

        /// <summary>
        /// Gets the parameter assignments for the passed activity sequence.
        /// </summary>
        /// <param name="baseActivitySequence">The base activity sequence.</param>
        /// <param name="paramAssignmentList">The list in which the parameter assignments should be added.</param>
        private static void GetParameterAssignments(SR.ActivitySequence baseActivitySequence, List<SR.ParameterAssignment> paramAssignmentList)
        {
            foreach (SR.ParameterAssignment paramAssign in baseActivitySequence.ParameterAssignments)
            {
                if (paramAssign.ActivityParameter.Direction == SR.DirectionType.Input && !paramAssign.IsBound)
                {
                    paramAssignmentList.Add(paramAssign);
                }
            }

            if (baseActivitySequence.Activity.IsComposite || baseActivitySequence.Activity.IsWorkflow)
            {
                foreach (SR.ActivitySequence child in baseActivitySequence.Children)
                {
                    GetParameterAssignments(child, paramAssignmentList);
                }
            }
        }
        #endregion
    }
}
